package server;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.util.Date;
import java.util.Properties;

import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLServerSocket;
import javax.net.ssl.SSLServerSocketFactory;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocket;
import javax.security.cert.X509Certificate;

import users.Doctor;
import users.Government;
import users.Nurse;
import users.Patient;
import users.User;

public class SocketConnection {
	private static final int ACCESS_GRANTED = 42;
	private static final int ACCESS_DENIED = 41;

	private static final int REQUEST_JOURNAL_LIST = 0;
	private static final int REQUEST_READ_FILE = 1;
	private static final int REQUEST_CREATE_FILE = 2;
	private static final int REQUEST_WRITE_FILE = 3;
	private static final int REQUEST_DELETE_FILE = 4;

	private static final String SERVER_END_OF_FILE = "EOF";

	private SSLServerSocket theServerSocket;

	private PrintWriter output;
	private BufferedReader input;
	private User user;

	public SocketConnection() {

		Properties systemProps = System.getProperties();
		systemProps.put("javax.net.ssl.trustStore", "../cert/ca/truststore");
		systemProps.put("javax.net.ssl.trustStorePassword", "password");
		System.setProperties(systemProps);

		char[] pass = "password".toCharArray();

		try {
			KeyStore ks = KeyStore.getInstance("JKS");
			ks.load(new FileInputStream("../cert/server/keystore"), pass);
			SSLContext sc = SSLContext.getInstance("TLS");

			KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
			kmf.init(ks, pass);

			sc.init(kmf.getKeyManagers(), null, null);
			SSLServerSocketFactory ssf = sc.getServerSocketFactory();
			theServerSocket = (SSLServerSocket) ssf.createServerSocket(13337);
			theServerSocket.setNeedClientAuth(true);
		} catch (IOException e) {
			e.printStackTrace();
		} catch (KeyStoreException e) {
			e.printStackTrace();
		} catch (NoSuchAlgorithmException e) {
			e.printStackTrace();
		} catch (CertificateException e) {
			e.printStackTrace();
		} catch (UnrecoverableKeyException e) {
			e.printStackTrace();
		} catch (KeyManagementException e) {
			e.printStackTrace();
		}
	}

	public void handleRequests() {
		SSLSocket socket = null;

		while (true) {
			try {
				System.out.println("Waiting for client connection...");
				socket = (SSLSocket) theServerSocket.accept();

				SSLSession session = socket.getSession();
				socket.startHandshake();

				X509Certificate cert = (X509Certificate) session
						.getPeerCertificateChain()[0];
				String subject = cert.getSubjectDN().getName();

				String[] split = subject.split(", ");
				System.out.println(subject);
				String cn = split[0].substring(3);
				String ou = split[1].substring(3);
				int o = Integer.parseInt(split[2].substring(2));

				user = null;

				switch (o) {
				case User.GOVERNMENT:
					user = new Government(cn);
					break;
				case User.DOCTOR:
					user = new Doctor(cn, ou);
					break;
				case User.NURSE:
					user = new Nurse(cn, ou);
					break;
				case User.PATIENT:
					user = new Patient(cn);
					break;
				}

				log("logged on successfully");

				output = new PrintWriter(socket.getOutputStream());
				input = new BufferedReader(new InputStreamReader(socket.getInputStream()));

				while (true) {
					Journal journal = null;
					String file = null;

					switch (input.read()) {
					case REQUEST_JOURNAL_LIST:
						File dir = new File("journals");
						File[] journals = dir.listFiles();

						StringBuilder sb = new StringBuilder();
						for (int i = 0; i < journals.length; i++) {
							if (journals[i].getName().startsWith(".")) {
								continue;
							}

							File[] contents = journals[i].listFiles();
							for (int j = 0; j < contents.length; j++) {
								if (contents[j].getName().startsWith(".")) {
									continue;
								}
								sb.append(journals[i].getName() + "/"
										+ contents[j].getName() + "\n");
							}
						}

						if (sb.toString().equals("")) {
							sb.append("NO FILES, PROGRAM WILL NOW PROBABLY CRASH\n");
						}

						output.write(ACCESS_GRANTED);
						output.write(sb.toString());
						output.write(SERVER_END_OF_FILE + "\n");
						output.flush();

						break;
					case REQUEST_READ_FILE:
						file = input.readLine();
						journal = new Journal(file);

						if (journal.canRead(user)) {
							output.write(ACCESS_GRANTED);
							output.write(journal.getData());
							output.write(SERVER_END_OF_FILE + "\n");
							log("got read access to file: "
									+ journal.getFilename());
						} else {
							output.write(ACCESS_DENIED);
							log("was denied read access to file: "
									+ journal.getFilename());
						}

						output.flush();

						break;
					case REQUEST_WRITE_FILE:
						file = input.readLine();
						journal = new Journal(file);

						if (journal.canWrite(user)) {
							output.write(ACCESS_GRANTED);
							output.flush();
							String journalContents = readAllIncomingText();
							journal.writeData(journalContents);
							log("got write access to file: "
									+ journal.getFilename());
						} else {
							output.write(ACCESS_DENIED);
							output.flush();
							log("was denied write access to file: "
									+ journal.getFilename());
						}

						break;
					case REQUEST_CREATE_FILE:
						if (user.getType() == User.DOCTOR) {
							output.write(ACCESS_GRANTED);
							output.flush();
							String patient = input.readLine();
							String nurse = input.readLine();

							journal = new Journal(new Patient(patient), user,
									new Nurse(nurse, user.getDivision()));
							log("got access to create file: "
									+ journal.getFilename());

						} else {
							output.write(ACCESS_DENIED);
							output.flush();
							log("was denied access to create a new file");
						}
						break;
					case REQUEST_DELETE_FILE:
						if (user.getType() == User.GOVERNMENT) {
							output.write(ACCESS_GRANTED);
							output.flush();

							String filename = input.readLine();
							File toDelete = new File("journals/" + filename);
							toDelete.delete();

							log("got access to delete file: " + filename);

						} else {
							output.write(ACCESS_DENIED);
							output.flush();
							log("was denied access to delete a file");
						}
						break;
					}

				}

			} catch (IOException e) {
				log("logged off");
			}
		}
	}

	private String readAllIncomingText() {
		StringBuilder sb = new StringBuilder();

		try {
			String line = "";
			for (int i = 0; !(line = input.readLine()).equals(SERVER_END_OF_FILE); i++) {
				sb.append(line + "\n");
			}
		} catch (IOException e) {
			e.printStackTrace();
		}

		return sb.toString();
	}

	private void log(String logEntry) {
		try {
			PrintWriter pw = new PrintWriter(new FileOutputStream(new File("log.txt"), true));
			pw.write(new Date(System.currentTimeMillis()) + " - User: " + user.getName() + " " + logEntry + "\n");
			pw.close();
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		}
	}
}
